﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Mail;
using System.Reflection;
using System.Text;
using Common.Utilities;
using Common.Utilities.Dispatcher;
using Common.Utilities.DLR;
using Workflows.Components;

namespace Workflows.SingleWorkflowConsoleApp
{
    class Program
    {
        private static bool _HaveFatalError = false;
        private static EventLog _Log;
        private static string EVENT_NAME = "Workflow";
        private static string EVENT_SORUCE = "Workflow";

        // email
        private static string _SMTPServerName;
        private static string _FromEmailAddress;
        private static string _UserName;
        private static string _Password;
        private static bool _RequireAuthentication;
        private static bool _EnableSSL;

        static void Main(string[] args)
        {
            RegisterEventLog();
            InitEmailSettings();

            string exeFilePath = Assembly.GetExecutingAssembly().Location;
            string binFolderPath = Path.GetDirectoryName(exeFilePath);
            string exeFileName = Path.GetFileNameWithoutExtension(exeFilePath);
            string settingFilePath = Path.Combine(binFolderPath, exeFileName + ".settings");
            if (!File.Exists(settingFilePath))
            {
                DirectoryInfo dirInfo=new DirectoryInfo(binFolderPath);
                FileInfo[] settingFiles = dirInfo.GetFiles("*.settings", SearchOption.TopDirectoryOnly);
                if (settingFiles != null && settingFiles.Length == 1)
                {
                    settingFilePath = settingFiles[0].FullName;
                }
                else
                {
                    DisplayError(
                        "Usage: You must provide a workflow setting file in the working direcotry, it should have extension \".settings\"");
                    return;
                }
            }
            WorkflowDesign wfDesign = new WorkflowDesign(settingFilePath);
            if (wfDesign == null)
            {
                DisplayError("Unable to initiate workflow design from setting file");
                return;
            }
            if (args != null && args.Length == 1 && (args[0] == "?" || args[0].ToLower() == "help"))
            {
                DisplayUsage(wfDesign);
                return;
            }

            // global variables
            Dictionary<string, string> inputNameValues = new Dictionary<string, string>();
            if (args.Length >= 1)
            {
                for (int i = 0; i < args.Length; i++)
                {
                    string arg = args[i];
                    string[] nameValueStr = arg.Split(new char[] {'='});
                    if (nameValueStr.Length == 2)
                    {
                        string argName = nameValueStr[0];
                        if (argName.StartsWith("\""))
                            argName = argName.Substring(1);
                        if (argName.EndsWith("\""))
                            argName = argName.Substring(0, argName.Length - 1);
                        string argValue = nameValueStr[1];
                        if (argValue.StartsWith("\""))
                            argValue = argValue.Substring(1);
                        if (argValue.EndsWith("\""))
                            argValue = argValue.Substring(0, argValue.Length - 1);
                        if (!inputNameValues.ContainsKey(argName.ToLower()))
                            inputNameValues.Add(argName.ToLower(), argValue);
                    }
                }
            }
            if (inputNameValues.Count > 0 && wfDesign.GlobalVariables != null && wfDesign.GlobalVariables.Length > 0)
            {
                for (int i = 0; i < wfDesign.GlobalVariables.Length; i++)
                {
                    GlobalVariable gVar = wfDesign.GlobalVariables[i];
                    if (inputNameValues.ContainsKey(gVar.VarName.ToLower()))
                    {
                        gVar.VarValue = inputNameValues[gVar.VarName.ToLower()];
                    }
                }
            }
            // context
            List<GlobalVariable> gVarList = new List<GlobalVariable>();
            if (wfDesign.GlobalVariables != null && wfDesign.GlobalVariables.Length > 0)
            {
                gVarList.AddRange(wfDesign.GlobalVariables);
            }
            RunContext context = new RunContext(Guid.NewGuid(), false, gVarList);
            // enabled components and dependencies 
            Dictionary<Guid, WorkflowComponentBase> wfComponents = UpdateDependencies(wfDesign);
            
            // init start component ids
            Guid triggerComponentID = Guid.Empty;
            foreach (WorkflowComponentBase wfComponent in wfComponents.Values)
            {
                if (wfComponent.GetType().IsSubclassOf(typeof (WorkflowTriggerBase)))
                {
                    triggerComponentID = wfComponent.ComponentID;
                    wfComponents.Remove(triggerComponentID);
                    break;
                }
            }
            Dictionary<Guid, ResultBase> finishedComponentOutputs = new Dictionary<Guid, ResultBase>();
            finishedComponentOutputs.Add(triggerComponentID, null);
            //foreach (WorkflowComponentBase wfComponent in wfComponents.Values)
            //{
            //    if (wfComponent.Dependency != null && wfComponent.Dependency.Count > 0)
            //    {
            //        if (wfComponent.Dependency.Count == 1 && wfComponent.Dependency[0] == triggerComponentID)
            //        {
            //            startingComponentIDs.Add(wfComponent.ComponentID);
            //        }
            //    }
            //    else
            //    {
            //        startingComponentIDs.Add(wfComponent.ComponentID);
            //    }
            //}
            List<Guid> startingComponentIDs = new List<Guid>();
            
            Dictionary<Type, Assembly> workerAssemblies = GetWorkerAssemblies();
            _HaveFatalError = false;
            while (wfComponents.Count > 0)
            {
                bool canContinue = false;
                startingComponentIDs.Clear();
                foreach (WorkflowComponentBase wfComponent in wfComponents.Values)
                {
                    if (wfComponent.Dependency != null && wfComponent.Dependency.Count > 0)
                    {
                        int dependentComponentFinishedCount = 0;
                        foreach (Guid dpID in wfComponent.Dependency)
                        {
                            if (finishedComponentOutputs.ContainsKey(dpID))
                            {
                                dependentComponentFinishedCount += 1;
                            }
                        }
                        if (dependentComponentFinishedCount == wfComponent.Dependency.Count)
                        {
                            startingComponentIDs.Add(wfComponent.ComponentID);
                        }
                    }
                    else
                    {
                        startingComponentIDs.Add(wfComponent.ComponentID);
                    }
                }

                foreach (Guid startingComponentID in startingComponentIDs)
                {
                    WorkflowComponentBase wfComponent = wfComponents[startingComponentID];
                    InstructionBase instruction = wfComponent.Instruction;
                    if (wfComponent.Dependency != null && wfComponent.Dependency.Count > 0 &&
                        finishedComponentOutputs.Count > 0)
                    {
                        instruction = ComputeInstructionForJob(
                            wfDesign, wfComponent, finishedComponentOutputs);

                        if(gVarList !=null && gVarList.Count>0)
                        {
                            instruction.BindToGlobalVariableValues(gVarList);
                        }
                    }

                    // dispatch
                    if(wfComponent.GetType()==typeof(GlobalVariableSetterComponent))
                    {
                        UpdateRunContext(context, finishedComponentOutputs,
                                         wfComponent.Instruction as GlobalVariableSetterInstruction);
                        GlobalVariableSetterOutput output = new GlobalVariableSetterOutput(
                            wfComponent.ComponentID, wfComponent.Instruction.OwnerWorkerInstanceID,
                            context.Vars);
                        finishedComponentOutputs.Add(startingComponentID, output);
                        wfComponents.Remove(startingComponentID);
                        canContinue = true;
                    }
                    else if (wfComponent.GetType() == typeof(GlobalVariableGetterComponent))
                    {
                        GlobalVariableGetterComponent getterComponent = wfComponent as GlobalVariableGetterComponent;
                        GlobalVariableGetterOutput getterOutput = GetGlobalGetterOutput(getterComponent, context);
                        if (getterOutput != null)
                            finishedComponentOutputs.Add(wfComponent.ComponentID, getterOutput);
                        else
                            finishedComponentOutputs.Add(startingComponentID, null);
                        wfComponents.Remove(startingComponentID);
                        canContinue = true;
                    }
                    else
                    {
                        foreach (Type workerType in workerAssemblies.Keys)
                        {
                            IWorker worker = (IWorker) Activator.CreateInstance(workerType);
                            if (worker.ComponentType == wfComponent.GetType())
                            {
                                canContinue = true;
                                worker.SetInstruction(context, Guid.NewGuid(), instruction, string.Empty, wfComponent.ComponentName);
                                worker.WorkerStartedEvent += new WorkerStarted(worker_WorkerStartedEvent);
                                worker.WorkerErredEvent += new WorkerHasError(worker_WorkerErredEvent);
                                worker.WorkerStatusChangedEvent +=
                                    new WorkerStatusChanged(worker_WorkerStatusChangedEvent);
                                worker.DoWork();
                                wfComponents.Remove(startingComponentID);
                                finishedComponentOutputs.Add(startingComponentID, worker.Output);
                                break;
                            }
                        }
                    }
                }
                if (!canContinue)
                {
                    DisplayError("Unable to execute component or unable to locate corresponding worker");
                    break;
                }

                if(_HaveFatalError)
                    break;

                
            }

            DisplayInfo("done!");

        }

        private static void DisplayUsage(WorkflowDesign wfDesign)
        {
            if(wfDesign.GlobalVariables !=null && wfDesign.GlobalVariables.Length>0)
            {
                DisplayInfo("Optional arguments to set global variables in workflow:");
                foreach(GlobalVariable gVar in wfDesign.GlobalVariables)
                {
                    DisplayInfo(string.Format("\"{0}\"=\"{1}\"",gVar.VarName,gVar.VarName + " value"));
                }
            }
            else
            {
                DisplayInfo("No argument is required");
            }
        }

        #region dependencies
        private static Dictionary<Guid, WorkflowComponentBase> UpdateDependencies(
            WorkflowDesign wfDesign)
        {
            Dictionary<Guid, WorkflowComponentBase> enabledComponents = new Dictionary<Guid, WorkflowComponentBase>();
            foreach (WorkflowComponentBase wfComponent in wfDesign.Components)
            {
                if (wfComponent.Enabled)
                {
                    enabledComponents.Add(wfComponent.ComponentID, wfComponent);
                }
            }

            Dictionary<Guid, List<Guid>> depencendiesByComponent = new Dictionary<Guid, List<Guid>>();
            if (wfDesign.Connectors != null && wfDesign.Connectors.Length > 0)
            {
                foreach (Connector connector in wfDesign.Connectors)
                {
                    if (depencendiesByComponent.ContainsKey(connector.ToComponentID))
                    {
                        List<Guid> dependencies = depencendiesByComponent[connector.ToComponentID];
                        if (!dependencies.Contains(connector.FromComponentID))
                            dependencies.Add(connector.FromComponentID);
                        depencendiesByComponent[connector.ToComponentID] = dependencies;
                    }
                    else
                    {
                        List<Guid> dependencies = new List<Guid>();
                        dependencies.Add(connector.FromComponentID);
                        depencendiesByComponent.Add(connector.ToComponentID, dependencies);
                    }
                }
            }

            Guid[] componentIDs=new Guid[enabledComponents.Count];
            enabledComponents.Keys.CopyTo(componentIDs,0);
            for (int i = 0; i < componentIDs.Length; i++)
            {
                Guid componentID = componentIDs[i];
                List<Guid> dependencies = new List<Guid>();
                GetUpstreamEnabledComponent(
                    componentID, enabledComponents, depencendiesByComponent, ref dependencies);
                enabledComponents[componentID].Dependency = dependencies;
            }

            return enabledComponents;
        }

        private static void GetUpstreamEnabledComponent(
            Guid currentComponentID, 
            Dictionary<Guid, WorkflowComponentBase> enabledComponents, 
            Dictionary<Guid, List<Guid>> dependencies,
            ref List<Guid> upstreamEnabledComponentIDs)
        {
            if (dependencies.ContainsKey(currentComponentID))
            {
                List<Guid> upstreamComponentIDs = dependencies[currentComponentID];
                for(int i=0;i<upstreamComponentIDs.Count;i++)
                {
                    Guid upstreamComponentID = upstreamComponentIDs[i];
                    if (enabledComponents.ContainsKey(upstreamComponentID))
                    {
                        if (!upstreamEnabledComponentIDs.Contains(upstreamComponentID))
                        {
                            upstreamEnabledComponentIDs.Add(upstreamComponentID);
                        }
                    }
                    else
                    {
                        if (dependencies.ContainsKey(upstreamComponentID))
                        {
                            GetUpstreamEnabledComponent(
                                upstreamComponentID, enabledComponents,
                                dependencies, ref upstreamEnabledComponentIDs);
                        }
                    }
                }
            }
        }
        #endregion

        #region data pipe
        /// <summary>
        /// Limitation:
        /// Instruction is instantiated from one type
        /// Unable to handle transformation, only assignment and type has to agree with each other
        /// </summary>
        /// <param name="currentComponent"></param>
        /// <param name="finishedComponentOutputs"></param>
        /// <param name="wfDesign"></param>
        /// <returns></returns>
        private static InstructionBase ComputeInstructionForJob(WorkflowDesign wfDesign, 
            WorkflowComponentBase currentComponent, Dictionary<Guid,ResultBase> finishedComponentOutputs)
        {
            InstructionBase instruction = currentComponent.Instruction;
            // 4: get field mappers
            if (instruction != null)
            {
                Dictionary<string, Type> writableInstructionFields =
                    instruction.GetBindableMemberNames(new Type[] { typeof(InstructionBase) });

                if (finishedComponentOutputs.Count > 0 && currentComponent.Dependency !=null && currentComponent.Dependency.Count>0)
                {
                    foreach (Guid finishedComponentID in finishedComponentOutputs.Keys)
                    {
                        if(currentComponent.Dependency.Contains(finishedComponentID))
                        {
                            WorkflowComponentBase upstreamComponent = wfDesign.GetComponent(finishedComponentID);
                            Connector wfConnector = wfDesign.GetConnector(upstreamComponent, currentComponent);
                            ResultBase jobOutput = finishedComponentOutputs[finishedComponentID];
                            if(jobOutput ==null)
                                continue;

                            Dictionary<string, Type> readableMembers =
                                jobOutput.GetBindableMemberNames(null);
                            if (wfConnector != null && wfConnector.FieldMappers != null)
                            {
                                foreach (CodeFieldMapper fieldMapper in wfConnector.FieldMappers)
                                {
                                    if (readableMembers.ContainsKey(fieldMapper.SrcFieldName) &&
                                        writableInstructionFields.ContainsKey(fieldMapper.TgtFieldName) &&
                                        wfConnector.FromComponentID == fieldMapper.SrcComponentID &&
                                        currentComponent.ComponentID == fieldMapper.TgtComponentID)
                                    {
                                        object inboundFieldValue =
                                            jobOutput.ReadMemberValue(fieldMapper.SrcFieldName);
                                        if (inboundFieldValue != null)
                                        {
                                            if (!string.IsNullOrEmpty(fieldMapper.AssignmentTransformation))
                                            {
                                                MethodArgument arg =
                                                    new MethodArgument(
                                                        "fromObj", inboundFieldValue.GetType(), 
                                                        false, inboundFieldValue);
                                                List<MethodArgument> args = new List<MethodArgument>();
                                                args.Add(arg);
                                                Type returnType = typeof(string);
                                                FieldInfo toField =
                                                    instruction.GetType().GetField(fieldMapper.TgtFieldName);
                                                if (toField != null)
                                                {
                                                    returnType = toField.FieldType;
                                                }
                                                else
                                                {
                                                    PropertyInfo toProp = instruction.GetType().GetProperty(fieldMapper.TgtFieldName);
                                                    if (toProp != null)
                                                    {
                                                        returnType = toProp.PropertyType;
                                                    }
                                                    // returnType = ((PropertyInfo)outboundField).PropertyType;
                                                }
                                                MethodConstructInfo methodBuilder =
                                                    new MethodConstructInfo(returnType, "Convert", args,
                                                                            fieldMapper.AssignmentTransformation);
                                                object tmp = CodeDomUtil.ExecuteMethod(methodBuilder);
                                                instruction.SetMemberValue(fieldMapper.TgtFieldName, tmp);
                                            }
                                            else
                                            {
                                                instruction.SetMemberValue(fieldMapper.TgtFieldName, inboundFieldValue);
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
                return instruction;
            }
            else
            {
                return null;
            }
        }

        private static void UpdateRunContext(
            RunContext context, 
            Dictionary<Guid, ResultBase> finishedComponentOutputs, 
            GlobalVariableSetterInstruction setterInstruction)
        {
            try
            {
                if (setterInstruction.Setters != null && setterInstruction.Setters.Count > 0 &&
                context.Vars != null && context.Vars.Count > 0)
                {
                    foreach (FieldSetter fieldSetter in setterInstruction.Setters)
                    {
                        for (int i = 0; i < context.Vars.Count; i++)
                        {
                            if (context.Vars[i].VarName == fieldSetter.TgtFieldName && 
                                finishedComponentOutputs.ContainsKey(fieldSetter.SrcComponentID))
                            {
                                GlobalVariable gVar = context.Vars[i];
                                ResultBase upstreamOutput = finishedComponentOutputs[fieldSetter.SrcComponentID];
                                object upstreamFieldValue = upstreamOutput.ReadMemberValue(fieldSetter.SrcFieldName);
                                if (!string.IsNullOrEmpty(fieldSetter.TransformationCodeSnippet))
                                {
                                    List<MethodArgument> args = new List<MethodArgument>();
                                    Type gVarType = SimpleDataTypeUtil.ToSystemType(gVar.DataType);
                                    MethodArgument arg0 =
                                        new MethodArgument(
                                            "gVarValue",
                                            gVarType,
                                            false,
                                            SimpleDataTypeUtil.ReadDbValue(gVar.DataType, gVar.VarValue));
                                    MethodArgument arg =
                                        new MethodArgument("fromObj", upstreamFieldValue.GetType(), false, upstreamFieldValue);
                                    args.Add(arg0);
                                    args.Add(arg);
                                    MethodConstructInfo methodBuilder =
                                        new MethodConstructInfo(
                                            gVarType,
                                            "Convert",
                                            args, fieldSetter.TransformationCodeSnippet);
                                    object gVarValue = CodeDomUtil.ExecuteMethod(methodBuilder);
                                    gVar.VarValue = SimpleDataTypeUtil.ReadValueAsString(gVar.DataType, gVarValue);
                                    context.Vars[i] = gVar;
                                }

                                break;
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                LogErrorMessage(ex.ToString());
            }
            
        }

        private static GlobalVariableGetterOutput GetGlobalGetterOutput(GlobalVariableGetterComponent wfComponent, RunContext context)
        {
            try
            {
                GlobalVariableGetterInstruction getterInstruction =
                    (GlobalVariableGetterInstruction) wfComponent.Instruction;
                GlobalVariableGetterOutput getterOutput = new GlobalVariableGetterOutput(
                    wfComponent.ComponentID, Guid.NewGuid(), new List<GlobalVariable>());
                if (getterInstruction.GlobalVariableNames != null && getterInstruction.GlobalVariableNames.Count > 0)
                {
                    foreach (string gVarName in getterInstruction.GlobalVariableNames)
                    {
                        if (context.Vars != null && context.Vars.Count > 0)
                        {
                            foreach (GlobalVariable gVar in context.Vars)
                            {
                                if (gVar.VarName == gVarName)
                                {
                                    getterOutput.GlobalVars.Add(gVar);
                                    break;
                                }
                            }
                        }
                    }
                }
                return getterOutput;
            }
            catch (Exception ex)
            {
                LogErrorMessage(ex.ToString());
                return null;
            }
        }
        #endregion

        #region notification
        private static void RegisterEventLog()
        {
            if (!EventLog.SourceExists(EVENT_SORUCE))
            {
                EventLog.CreateEventSource(EVENT_SORUCE, EVENT_NAME);
            }
            _Log = new EventLog(EVENT_NAME, ".", EVENT_SORUCE);
            _Log.MaximumKilobytes = 4096;
            _Log.ModifyOverflowPolicy(OverflowAction.OverwriteAsNeeded, 7);
        }

        private static void InitEmailSettings()
        {
            _SMTPServerName = ConfigurationManager.AppSettings.Get("SmtpServer");
            _FromEmailAddress = ConfigurationManager.AppSettings.Get("FromEmailAddress");
            _UserName = ConfigurationManager.AppSettings.Get("EmailUserName");
            _Password = ConfigurationManager.AppSettings.Get("EmailPassword");
            _RequireAuthentication = bool.Parse(ConfigurationManager.AppSettings.Get("RequireAuthentication"));
            _EnableSSL = bool.Parse(ConfigurationManager.AppSettings.Get("EnableSSL"));
        }

        static void worker_WorkerStartedEvent(Guid ticketID, IWorker workerReference, Type workerType, InstructionBase instructionBase)
        {
            DisplayInfo(string.Format("{0}: Workflow {1} started.", DateTime.Now.ToShortTimeString(), workerType.Name));
        }

        static void worker_WorkerStatusChangedEvent(Guid ticketID, Type workerType, InstructionBase instructionBase,
            string workflowName, string componentName, string message, int percent)
        {
            string msg = string.Format("{0}: Running workflow {1}({2}), {3} percent finished...{4}",
                                       DateTime.Now.ToShortTimeString(), componentName,
                                       workerType.Name, percent, message);
            LogInfo(msg);
            DisplayInfo(msg);
        }

        static void worker_WorkerErredEvent(Guid ticketID, Type workerType, InstructionBase instructionBase, string error, bool isFatal)
        {
            LogErrorMessage(error);
            DisplayError(string.Format("{0}: Error in workflow {1}, {2}",
                                       DateTime.Now.ToShortTimeString(), workerType.Name, error));

            if (isFatal)
            {
                SendNotification(new string[] { _FromEmailAddress }, null, "Workflow Error", error);
                _HaveFatalError = true;
            }
        }

        private static Dictionary<Type,Assembly> GetWorkerAssemblies()
        {
            Dictionary<Type,Assembly> workerAssemblies=new Dictionary<Type, Assembly>();
            string binFolderPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
            DirectoryInfo dirInfo = new DirectoryInfo(binFolderPath);
            FileInfo[] fileInfos = dirInfo.GetFiles("*.dll", SearchOption.TopDirectoryOnly);
            foreach(FileInfo fileInfo in fileInfos)
            {
                if(fileInfo.Name.ToLower().StartsWith("discoverylogic") || fileInfo.Name.ToLower().StartsWith("workflow"))
                {
                    try
                    {
                        Assembly assembly = Assembly.LoadFile(fileInfo.FullName);
                        Type[] types = assembly.GetTypes();
                        foreach(Type type in types)
                        {
                            if(type.GetInterface(typeof(IWorker).FullName) !=null)
                            {
                                workerAssemblies.Add(type, assembly);
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        DisplayError(ex.ToString());
                    }
                }
            }
            return workerAssemblies;
        }

        private static void DisplayInfo(string info)
        {
            Console.ForegroundColor = ConsoleColor.White;
            Console.BackgroundColor = ConsoleColor.Black;
            Console.WriteLine(info);
        }

        private static void LogErrorMessage(string msg)
        {
            if (msg.Length > 3990)
                msg = msg.Substring(0, 3990) + "...";
            _Log.WriteEntry(msg, EventLogEntryType.Error);
        }

        private static void LogInfo(string msg)
        {
            if (msg.Length > 3990)
                msg = msg.Substring(0, 3990) + "...";
            _Log.WriteEntry(msg, EventLogEntryType.Information);
        }

        private static void DisplayError(string error)
        {
            Console.ForegroundColor = ConsoleColor.Red;
            Console.BackgroundColor = ConsoleColor.Black;
            Console.WriteLine(error);
            Console.ForegroundColor = ConsoleColor.White;
        }

        private static void SendNotification(
            string[] toAddresses, string[] ccAddresses,
            string subj, string body)
        {
            try
            {
                MailMessage message = new MailMessage();
                message.From = new MailAddress(_FromEmailAddress);
                foreach (string toAddr in toAddresses)
                {
                    message.To.Add(new MailAddress(toAddr));
                }
                if (ccAddresses != null && ccAddresses.Length > 0)
                {
                    foreach (string ccAddr in ccAddresses)
                    {
                        message.CC.Add(new MailAddress(ccAddr));
                    }
                }
                message.Subject = subj;
                message.Body = body;
                message.Priority = MailPriority.High;
                message.IsBodyHtml = true;
                SmtpClient smtp = new SmtpClient(_SMTPServerName);
                if (_RequireAuthentication)
                {
                    smtp.Credentials = new NetworkCredential(_UserName, _Password);
                }
                if (_EnableSSL)
                {
                    smtp.EnableSsl = true;
                }
                smtp.SendAsync(message, null);
            }
            catch (Exception ex)
            {
                LogErrorMessage(ex.ToString());
                LogErrorMessage(ex.StackTrace);
            }
        }
        #endregion
    }
}
